/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sys/epoll.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <linux/virtio_net.h>
#include <sys/types.h>
#include <dirent.h>
#include "virtnet.h"

int virtnet_rec_file_create(const char *file_name)
{
	char full_name[FILE_PATH_SIZE];
	FILE *fp = NULL;
	int ret = 0;

	snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH, file_name);
	if (access(full_name, F_OK) == 0)
		return 0;

	log_debug("Create recovery file %s", full_name);
	fp = fopen(full_name, "w");
	if (!fp) {
		log_error("Failed to create recovery file: %d", errno);
		return -EINVAL;
	}
	if (1 != fwrite("{\n}", JSON_EMPTY_SIZE, 1, fp)) {
		log_error("Failed to write recovery file: %d", errno);
		ret = -EINVAL;
	}

	fsync(fileno(fp));
	fclose(fp);
	return ret;
}

bool virtnet_rec_file_exist(const char *file_name)
{
	char full_name[FILE_PATH_SIZE];

	if (access(RECOVERY_PATH, F_OK) == 0) {
		snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH,
			 file_name);
		return !access(full_name, F_OK);
	}

	mkdir(RECOVERY_PATH, 0755);
	return false;
}

int virtnet_rec_file_rename(const char *file_name, const char *file_name_new)
{
	char full_name[FILE_PATH_SIZE];
	char full_name_new[FILE_PATH_SIZE];

	snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH, file_name);
	snprintf(full_name_new, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH,
		 file_name_new);

	return rename(full_name, full_name_new);
}

int virtnet_rec_file_del(const char *file_name)
{
	char full_name[FILE_PATH_SIZE];

	snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH, file_name);

	return remove(full_name);
}

static void virtnet_rec_json_mactable_fill(cJSON *jsoncfg, const void *key,
					   const char *val, bool save)
{
	cJSON *array = cJSON_GetObjectItem(jsoncfg, (const char *)key);
	cJSON *array_item, *mac_item;
	bool found = false;
	int i;

	log_debug("mac: %s", val);

	if (array) {
		for (i = cJSON_GetArraySize(array) - 1; i >= 0; i--) {
			array_item = cJSON_GetArrayItem(array, i);
			mac_item = cJSON_GetObjectItem(array_item, "mac");
			log_debug("mac_item: %s", mac_item->valuestring);
			if (!strcmp(mac_item->valuestring, val)) {
				log_debug("found %s", mac_item->valuestring);
				found = true;
				break;
			}
		}
		if (found) {
			if (!save)
				cJSON_DeleteItemFromArray(array, i);
		} else {
			array_item = cJSON_CreateObject();
			mac_item = cJSON_CreateString(val);
			cJSON_AddItemToObject(array_item, "mac", mac_item);
			cJSON_AddItemToArray(array, array_item);
		}
	} else {
		if (!save)
			return;
		array = cJSON_CreateArray();
		array_item = cJSON_CreateObject();
		mac_item = cJSON_CreateString(val);
		cJSON_AddItemToObject(array_item, "mac", mac_item);
		cJSON_AddItemToArray(array, array_item);
		cJSON_AddItemToObject(jsoncfg, (const char *)key, array);
	}
}

static void virtnet_rec_json_str_fill(cJSON *jsoncfg, const char *key,
				      const char *val, bool save)
{
	cJSON *thekey = cJSON_GetObjectItem(jsoncfg, key);
	cJSON *val_item;

	if (thekey) {
		val_item = cJSON_CreateString(val);
		if (save)
			cJSON_ReplaceItemInObject(jsoncfg, key, val_item);
		else
			cJSON_DeleteItemFromObject(jsoncfg, key);
	} else {
		if (!save)
			return;
		val_item = cJSON_CreateString(val);
		cJSON_AddItemToObject(jsoncfg, key, val_item);
	}
}

static void virtnet_rec_json_num_fill(cJSON *jsoncfg, const char *key,
				      int num, bool save)
{
	cJSON *thekey = cJSON_GetObjectItem(jsoncfg, key);
	cJSON *val_item;

	if (thekey) {
		val_item = cJSON_CreateNumber(num);
		if (save)
			cJSON_ReplaceItemInObject(jsoncfg, key, val_item);
		else
			cJSON_DeleteItemFromObject(jsoncfg, key);
	} else {
		if (!save)
			return;
		val_item = cJSON_CreateNumber(num);
		cJSON_AddItemToObject(jsoncfg, key, val_item);
	}
}

static void virtnet_rec_json_config_fill(cJSON *jsoncfg, enum rec_key key,
					 const struct virtnet_rec *rec, bool save)
{
	char str[STR_SIZE];

	switch (key) {
	case REC_KEY_PF_ID:
		virtnet_rec_json_num_fill(jsoncfg, "pf_id", rec->pf_id, save);
		break;
	case REC_KEY_VF_ID:
		virtnet_rec_json_num_fill(jsoncfg, "vf_id", rec->vf_id, save);
		break;
	case REC_KEY_FUNC_TYPE:
		switch (rec->function_type) {
		case VIRTNET_DEV_PF:
			strcpy(str, "pf");
			break;
		case VIRTNET_DEV_VF:
			strcpy(str, "vf");
			break;
		default:
			return;
		}
		virtnet_rec_json_str_fill(jsoncfg, "function_type", str, save);
		break;
	case REC_KEY_DEV_TYPE:
		virtnet_rec_json_str_fill(jsoncfg, "device_type",
					  rec->device_type, save);
		break;
	case REC_KEY_BDF_RAW:
		virtnet_rec_json_num_fill(jsoncfg, "bdf_raw", rec->bdf_raw, save);
		break;
	case REC_KEY_SF_PAR_DEV:
		virtnet_rec_json_str_fill(jsoncfg, "sf_parent_device",
					  rec->sf_parent_device, save);
		break;
	case REC_KEY_PF_NUM:
		virtnet_rec_json_num_fill(jsoncfg, "pf_num", rec->pf_num,
					  save);
		break;
	case REC_KEY_SF_NUM:
		virtnet_rec_json_num_fill(jsoncfg, "sf_num", rec->sf_num,
					  save);
		break;
	case REC_KEY_RX_MODE_CMD:
		virtnet_rec_json_num_fill(jsoncfg, "rx_mode_cmd",
					  rec->rx_mode_cmd, save);
		break;
	case REC_KEY_RX_MODE:
		virtnet_rec_json_num_fill(jsoncfg, "rx_mode", rec->rx_mode,
					  save);
		break;
	case REC_KEY_MAC_CMD:
		virtnet_rec_json_num_fill(jsoncfg, "mac_cmd", rec->mac_cmd,
					  save);
		break;
	case REC_KEY_MAC:
		snprintf(str, STR_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x",
			 rec->mac[0], rec->mac[1], rec->mac[2], rec->mac[3],
			 rec->mac[4], rec->mac[5]);
		virtnet_rec_json_str_fill(jsoncfg, "mac", str, save);
		break;
	case REC_KEY_MAC_TABLE:
		snprintf(str, STR_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x",
			 rec->mac[0], rec->mac[1], rec->mac[2], rec->mac[3],
			 rec->mac[4], rec->mac[5]);
		virtnet_rec_json_mactable_fill(jsoncfg, "mac_table", str,
						save);
		break;
	case REC_KEY_VLAN_TAB_CMD:
		virtnet_rec_json_num_fill(jsoncfg, "vlan_table_cmd",
					  rec->vlan_table_cmd, save);
		break;
	case REC_KEY_VID:
		virtnet_rec_json_num_fill(jsoncfg, "vid", rec->vid, save);
		break;
	case REC_KEY_ANNO_CMD:
		virtnet_rec_json_num_fill(jsoncfg, "announce_cmd",
					  rec->announce_cmd, save);
		break;
	case REC_KEY_ANNO:
		virtnet_rec_json_num_fill(jsoncfg, "announce",
					  rec->announce, save);
		break;
	case REC_KEY_MQ:
		virtnet_rec_json_num_fill(jsoncfg, "mq", rec->mq, save);
		break;
	}
}

int virtnet_rec_config_handle(const char *file_name, const enum rec_key *keys,
			       int size, const struct virtnet_rec *rec,
			       bool save)
{
	char full_name[FILE_PATH_SIZE];
	cJSON *jsoncfg = NULL;
	char *jsonres = NULL;
	char *buffer = NULL;
	FILE *fp = NULL;
	enum rec_key key;
	int ret = 0, i;
	long lSize;

	ret = virtnet_rec_file_create(file_name);
	if (ret)
		return ret;

	snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH, file_name);
	fp = fopen(full_name, "r+");
	if (!fp) {
		log_error("Failed to open file: %d", errno);
		return -EINVAL;
	}

	fseek(fp, 0L, SEEK_END);
	lSize = ftell(fp);
	rewind(fp);

	if (lSize) {
		/* allocate memory for entire content */
		buffer = calloc(1, lSize + 1);
		if (!buffer) {
			ret = -ENOMEM;
			log_error("Failed to allocate memory");
			goto out_close;
		}

		/* copy the file into the buffer */
		if (1 != fread(buffer, lSize, 1, fp)) {
			ret = -EINVAL;
			log_error("Failed to read file: %d", errno);
			goto out_close;
		}
	} else {
		buffer = calloc(1, JSON_EMPTY_SIZE);
		if (!buffer) {
			ret = -ENOMEM;
			log_error("Failed to allocate memory");
			goto out_close;
		}
		snprintf(buffer, JSON_EMPTY_SIZE, "{\n}");
	}

	jsoncfg = cJSON_Parse(buffer);
	if (!jsoncfg) {
		ret = -EINVAL;
		log_error("Wrong format of this JSON file");
		goto out_parse;
	}

	for (i = 0; i < size; ++i) {
		key = keys[i];
		virtnet_rec_json_config_fill(jsoncfg, key, rec, save);
	}

	jsonres = cJSON_Print(jsoncfg);

	fseek(fp, 0L, SEEK_SET);
	if (1 != fwrite(jsonres, strlen(jsonres), 1, fp)) {
		ret = -EINVAL;
		log_error("Failed to write file: %d", errno);
		goto out_free;
	}
	fsync(fileno(fp));

out_free:
	free(jsonres);
	cJSON_Delete(jsoncfg);
out_parse:
	free(buffer);
out_close:
	fclose(fp);
	return ret;
}

int virtnet_rec_str_handle(const char *file_name, enum rec_key key, const char *val,
			   bool save)
{
	struct virtnet_rec rec;
	int ret;

	switch (key) {
	case (REC_KEY_DEV_TYPE):
		strncpy(rec.device_type, val, STR_SIZE);
		break;
	case (REC_KEY_SF_PAR_DEV):
		strncpy(rec.sf_parent_device, val, STR_SIZE);
		break;
	default:
		ret = -EINVAL;
		return ret;
	}

	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, save);

	return ret;
}

int virtnet_rec_num_handle(const char *file_name, enum rec_key key, uint32_t val,
			   bool save)
{
	struct virtnet_rec rec;
	int ret;

	switch (key) {
	case (REC_KEY_PF_ID):
		rec.pf_id = (uint16_t)val;
		break;
	case (REC_KEY_VF_ID):
		rec.vf_id = (uint16_t)val;
		break;
	case (REC_KEY_FUNC_TYPE):
		rec.function_type = (uint8_t)val;
		break;
	case (REC_KEY_BDF_RAW):
		rec.bdf_raw = (uint16_t)val;
		break;
	case (REC_KEY_PF_NUM):
		rec.pf_num = (uint32_t)val;
		break;
	case (REC_KEY_SF_NUM):
		rec.sf_num = (uint32_t)val;
		break;
	case (REC_KEY_RX_MODE_CMD):
		rec.rx_mode_cmd = (uint8_t)val;
		break;
	case (REC_KEY_RX_MODE):
		rec.rx_mode = (uint8_t)val;
		break;
	case (REC_KEY_MAC_CMD):
		rec.mac_cmd = (uint8_t)val;
		break;
	case (REC_KEY_VLAN_TAB_CMD):
		rec.vlan_table_cmd = (uint8_t)val;
		break;
	case (REC_KEY_VID):
		rec.vid = (uint16_t)val;
		break;
	case (REC_KEY_ANNO_CMD):
		rec.announce_cmd = (uint8_t)val;
		break;
	case (REC_KEY_ANNO):
		rec.announce = (uint8_t)val;
		break;
	case (REC_KEY_MQ):
		rec.mq = (uint16_t)val;
		break;
	default:
		ret = -EINVAL;
		return ret;
	}

	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, save);

	return ret;
}

int virtnet_rec_mac_save(const char *file_name, const uint8_t *mac)
{
	enum rec_key key = REC_KEY_MAC;
	struct virtnet_rec rec;
	int ret;

	memcpy(rec.mac, mac, ETH_ALEN);

	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, true);

	return ret;
}

int virtnet_rec_mac_del(const char *file_name, const uint8_t *mac)
{
	enum rec_key key = REC_KEY_MAC;
	struct virtnet_rec rec;
	int ret;

	memcpy(rec.mac, mac, ETH_ALEN);
	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, false);

	return ret;
}

int virtnet_rec_mactable_entry_add(const char *file_name, const uint8_t *mac)
{
	enum rec_key key = REC_KEY_MAC_TABLE;
	struct virtnet_rec rec;
	int ret;

	memcpy(rec.mac, mac, ETH_ALEN);
	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, true);

	return ret;
}

int virtnet_rec_mactable_entry_del(const char *file_name, const uint8_t *mac)
{
	enum rec_key key = REC_KEY_MAC_TABLE;
	struct virtnet_rec rec;
	int ret;

	memcpy(rec.mac, mac, ETH_ALEN);
	ret = virtnet_rec_config_handle(file_name, &key, 1, &rec, false);

	return ret;
}

static int virtnet_rec_json_mactable_load(cJSON *jsoncfg, const void *key,
					  uint8_t **vals)
{
	cJSON *array = cJSON_GetObjectItem(jsoncfg, (const char *)key);
	cJSON *array_item, *mac_item;
	int i, size;

	if (array) {
		size = cJSON_GetArraySize(array);
		*vals = (uint8_t *)calloc(size, sizeof(void *));
		for (i = size - 1; i >= 0; i--) {
			uint8_t *mac = *vals + i * sizeof(void *);
			array_item = cJSON_GetArrayItem(array, i);
			mac_item = cJSON_GetObjectItem(array_item, "mac");
			sscanf(mac_item->valuestring,
			       "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
			       &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
		}
		return size;
	}

	return 0;
}

int virtnet_rec_config_load(const char *file_name, enum rec_key key,
		     struct virtnet_rec *rec)
{
	char full_name[FILE_PATH_SIZE];
	cJSON *json_value = NULL;
	cJSON *jsoncfg = NULL;
	char *buffer = NULL;
	FILE *fp = NULL;
	long lSize = 0;
	int ret = 0;

	if (!virtnet_rec_file_exist(file_name)) {
		return -ENOENT;
	}

	snprintf(full_name, FILE_PATH_SIZE, "%s/%s", RECOVERY_PATH, file_name);
	fp = fopen(full_name, "rb");
	if (!fp) {
		log_error("Failed to open file: %d", errno);
		return -EINVAL;
	}

	fseek(fp, 0L, SEEK_END);
	lSize = ftell(fp);
	rewind(fp);

	/* allocate memory for entire content */
	buffer = calloc(1, lSize + 1);
	if (!buffer) {
		ret = -ENOMEM;
		log_error("Failed to allocate memory");
		goto out_close;
	}

	/* copy the file into the buffer */
	if (1 != fread(buffer, lSize, 1, fp)) {
		ret = -EINVAL;
		log_error("Failed to read file: %d", errno);
		goto out_free;
	}

	jsoncfg = cJSON_Parse(buffer);
	if (!jsoncfg) {
		ret = -EINVAL;
		log_error("Wrong format for this JSON file");
		goto out_free;
	}

	switch (key) {
	case REC_KEY_PF_ID:;
		json_value = cJSON_GetObjectItem(jsoncfg, "pf_id");
		if (json_value)
			rec->pf_id = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_VF_ID:;
		json_value = cJSON_GetObjectItem(jsoncfg, "vf_id");
		if (json_value)
			rec->vf_id = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_FUNC_TYPE:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "function_type");
		if (json_value) {
			switch (json_value->valuestring[0]) {
			case 'p':
				rec->function_type = VIRTNET_DEV_PF;
				break;
			case 'v':
				rec->function_type = VIRTNET_DEV_PF;
				break;
			}
		} else
			ret = -EINVAL;
		break;
	case REC_KEY_DEV_TYPE:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "device_type");
		if (json_value)
			snprintf(rec->device_type, STR_SIZE, "%s",
				 json_value->valuestring);
		else
			ret = -EINVAL;
		break;
	case REC_KEY_BDF_RAW:;
		json_value = cJSON_GetObjectItem(jsoncfg, "bdf_raw");
		if (json_value)
			rec->bdf_raw = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_SF_PAR_DEV:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "sf_parent_device");
		if (json_value)
			snprintf(rec->sf_parent_device, STR_SIZE, "%s",
				 json_value->valuestring);
		else
			ret = -EINVAL;
		break;
	case REC_KEY_PF_NUM:;
		json_value = cJSON_GetObjectItem(jsoncfg, "pf_num");
		if (json_value)
			rec->pf_num = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_SF_NUM:;
		json_value = cJSON_GetObjectItem(jsoncfg, "sf_num");
		if (json_value)
			rec->sf_num = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_RX_MODE_CMD:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "rx_mode_cmd");
		if (json_value)
			rec->rx_mode_cmd = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_RX_MODE:;
		json_value = cJSON_GetObjectItem(jsoncfg, "rx_mode");
		if (json_value)
			rec->rx_mode = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_MAC_CMD:;
		json_value = cJSON_GetObjectItem(jsoncfg, "mac_cmd");
		if (json_value)
			rec->mac_cmd = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_MAC_TABLE:;
		log_debug("rec->mac_table: %p", rec->mac_table);
		ret = virtnet_rec_json_mactable_load(jsoncfg, "mac_table",
						   rec->mac_table);
		break;
	case REC_KEY_VLAN_TAB_CMD:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "vlan_table_cmd");
		if (json_value)
			rec->vlan_table_cmd = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_VID:;
		json_value = cJSON_GetObjectItem(jsoncfg, "vid");
		if (json_value)
			rec->vid = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_ANNO_CMD:;
		json_value =
			cJSON_GetObjectItem(jsoncfg, "announce_cmd");
		if (json_value)
			rec->announce_cmd = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_ANNO:;
		json_value = cJSON_GetObjectItem(jsoncfg, "announce");
		if (json_value)
			rec->announce = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_MQ:;
		json_value= cJSON_GetObjectItem(jsoncfg, "mq");
		if (json_value)
			rec->mq = json_value->valueint;
		else
			ret = -EINVAL;
		break;
	case REC_KEY_MAC:;
		json_value= cJSON_GetObjectItem(jsoncfg, "mac");
		if (json_value) {
			char *str = json_value->valuestring;
			uint8_t *_mac = (uint8_t *)&rec->mac;
			if (strlen(str) != 17) {
				log_error("Mac addr is not valid");
				ret = -EINVAL;
				break;
			}
			sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &_mac[0],
			       &_mac[1], &_mac[2], &_mac[3], &_mac[4],
			       &_mac[5]);
		} else {
			ret = -EINVAL;
		}
		break;
	}

	cJSON_Delete(jsoncfg);
out_free:
	free(buffer);
out_close:
	fclose(fp);
	return ret;
}

int virtnet_rec_str_load(const char *file_name, enum rec_key key, char *val)
{
	struct virtnet_rec rec;
	int ret;

	ret = virtnet_rec_config_load(file_name, key, &rec);
	if (ret)
		return ret;

	switch (key) {
	case (REC_KEY_DEV_TYPE):
		strncpy(val, rec.device_type, STR_SIZE);
		break;
	case (REC_KEY_SF_PAR_DEV):
		strncpy(val, rec.sf_parent_device, STR_SIZE);
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

int virtnet_rec_num_load(const char *file_name, enum rec_key key, void *val)
{
	struct virtnet_rec rec;
	int ret;

	ret = virtnet_rec_config_load(file_name, key, &rec);
	if (ret)
		return ret;

	switch (key) {
	case (REC_KEY_PF_ID):
		*(uint16_t *)val = rec.pf_id;
		break;
	case (REC_KEY_VF_ID):
		*(uint16_t *)val = rec.vf_id;
		break;
	case (REC_KEY_FUNC_TYPE):
		*(uint8_t *)val = rec.function_type;
		break;
	case (REC_KEY_BDF_RAW):
		*(uint16_t *)val = rec.bdf_raw;
		break;
	case (REC_KEY_PF_NUM):
		*(uint32_t *)val = rec.pf_num;
		break;
	case (REC_KEY_SF_NUM):
		*(uint32_t *)val = rec.sf_num;
		break;
	case (REC_KEY_RX_MODE_CMD):
		*(uint8_t *)val = rec.rx_mode_cmd;
		break;
	case (REC_KEY_RX_MODE):
		*(uint8_t *)val = rec.rx_mode;
		break;
	case (REC_KEY_MAC_CMD):
		*(uint8_t *)val = rec.mac_cmd;
		break;
	case (REC_KEY_VLAN_TAB_CMD):
		*(uint8_t *)val = rec.vlan_table_cmd;
		break;
	case (REC_KEY_VID):
		*(uint16_t *)val = rec.vid;
		break;
	case (REC_KEY_ANNO_CMD):
		*(uint8_t *)val = rec.announce_cmd;
		break;
	case (REC_KEY_ANNO):
		*(uint8_t *)val = rec.announce;
		break;
	case (REC_KEY_MQ):
		*(uint16_t *)val = rec.mq;
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

int virtnet_rec_mac_load(const char *file_name, uint8_t *mac)
{
	struct virtnet_rec rec;
	int ret;

	ret = virtnet_rec_config_load(file_name, REC_KEY_MAC, &rec);
	if (ret)
		return ret;

	memcpy(mac, rec.mac, ETH_ALEN);

	return 0;
}

int virtnet_rec_mactable_load(const char *file_name, uint8_t **macs)
{
	struct virtnet_rec rec;
	int ret;

	rec.mac_table = macs;
	ret = virtnet_rec_config_load(file_name, REC_KEY_MAC_TABLE, &rec);

	return ret;
}

void virtnet_device_check_recover(struct virtnet_device *dev)
{
	char bdf_str[STR_SIZE] = {};

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	if (virtnet_rec_file_exist(bdf_str)) {
		dev->recover = true;
		log_debug("Found recover file for %s, start recovery flow", bdf_str);
	} else {
		dev->recover = false;
	}
}

bool virtnet_device_is_recovering(struct virtnet_device *dev)
{
	return dev->recover;
}

int virtnet_device_rec_init(struct virtnet_device *dev)
{
	struct virtnet_rec data = {};
	enum rec_key pf_keys[] = {
		REC_KEY_PF_ID,
		REC_KEY_FUNC_TYPE,
		REC_KEY_BDF_RAW,
		REC_KEY_DEV_TYPE,
		REC_KEY_MAC,
		REC_KEY_PF_NUM,
		REC_KEY_SF_NUM,
	};
	enum rec_key vf_keys[] = {
		REC_KEY_PF_ID,
		REC_KEY_VF_ID,
		REC_KEY_FUNC_TYPE,
		REC_KEY_BDF_RAW,
		REC_KEY_MAC,
		REC_KEY_PF_NUM,
		REC_KEY_SF_NUM,
	};
	char bdf_str[STR_SIZE];
	uint64_t mac_raw;
	uint8_t *mac;
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_file_create(bdf_str);
	if (ret) {
		log_error("Failed to create recovery file for dev %s", bdf_str);
		return ret;
	}

	mac_raw = htobe64(dev->registers->mac);
	mac = (uint8_t *)&mac_raw;
	memcpy(data.mac, &mac[2], ETH_ALEN);
	data.bdf_raw = dev->snap_pci->pci_bdf.raw;
	data.pf_num = dev->port_ctx->pci_function;
	data.sf_num = dev->sf_verbs.sf_num;

	if (dev->flag & VIRTNET_DEV_PF) {
		data.pf_id = dev->snap_pci->id;
		data.function_type = VIRTNET_DEV_PF;
		strncpy(data.device_type,
			(dev->flag & VIRTNET_DEV_PRE_CONFIG) ?
			"static" : "hotplug", STR_SIZE);
		ret = virtnet_rec_config_save(bdf_str, pf_keys,
					sizeof(pf_keys)/sizeof(enum rec_key),
					&data);
		if (ret)
			goto err_save;
	} else if (dev->flag & VIRTNET_DEV_VF) {
		data.pf_id = dev->snap_pci->parent->id;
		data.vf_id = dev->snap_pci->id;
		data.function_type = VIRTNET_DEV_VF;
		ret = virtnet_rec_config_save(bdf_str, vf_keys,
					sizeof(vf_keys)/sizeof(enum rec_key),
					&data);
		if (ret)
			goto err_save;
	}

	return 0;

err_save:
	virtnet_rec_file_del(bdf_str);
	return ret;
}

int virtnet_device_rec_destroy(struct virtnet_device *dev)
{
	char bdf_str[STR_SIZE];

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	return virtnet_rec_file_del(bdf_str);
}

void virtnet_device_devtype_recover(struct virtnet_device *dev)
{
	char bdf_str[STR_SIZE];
	char str[STR_SIZE];

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	if (!virtnet_rec_device_type_load(bdf_str, str))
		if (str[0] == 'h')
			dev->flag &= ~VIRTNET_DEV_PRE_CONFIG;
}

int virtnet_device_mac_recover(struct virtnet_device *dev)
{
	struct snap_virtio_net_device_attr nattr = {};
	struct snap_device *sdev = dev->snap_ctrl->common.sdev;
	char bdf_str[STR_SIZE];
	uint8_t *mac;
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = snap_virtio_net_query_device(sdev, &nattr);
	if (ret)
		return ret;
	mac = (uint8_t *)&nattr.mac;
	ret = virtnet_rec_mac_load(bdf_str, &mac[2]);
	if (ret) {
		log_error("Failed to load mac for dev %s", bdf_str);
		return ret;
	}

	nattr.mac = be64toh(nattr.mac);
	ret = snap_virtio_net_modify_device(sdev, SNAP_VIRTIO_MOD_DEV_CFG, &nattr);

	return ret;
}

int virtnet_device_mac_save(struct virtnet_device *dev, uint8_t *mac_octet)
{
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_file_create(bdf_str);
	if (ret) {
		log_error("Failed to create recovery file for dev %s", bdf_str);
		return ret;
	}

	ret = virtnet_rec_mac_save(bdf_str, mac_octet);
	if (ret)
		log_error("Failed to save mac for dev %s", bdf_str);

	return ret;
}

int virtnet_device_rxmode_recover(struct virtnet_device *dev)
{
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_rx_mode_load(bdf_str, &dev->rx_mode.val);
	if (!ret) {
		ret = virtnet_sf_set_rx_mode(dev);
		if (ret) {
			log_error("Failed to set rx_mode for dev %s", bdf_str);
			return ret;
		}
	} else {
		log_error("Failed to load rx mode for dev %s", bdf_str);
	}

	return 0;
}

int virtnet_device_rxmode_save(struct virtnet_device *dev, uint8_t rxmode)
{
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_file_create(bdf_str);
	if (ret) {
		log_error("Failed to create recovery file for dev %s", bdf_str);
		return ret;
	}

	ret = virtnet_rec_rx_mode_save(bdf_str, rxmode);
	if (ret)
		log_error("Failed to save rx mode for dev %s", bdf_str);

	return ret;
}

int virtnet_device_mq_recover(struct virtnet_device *dev)
{
	char bdf_str[STR_SIZE];
	uint16_t mq;
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_mq_load(bdf_str, &mq);
	if (!ret) {
		ret = virtnet_sf_mq_update(dev, mq);
		if (ret) {
			log_error("Failed to set mq for dev %s", bdf_str);
			return ret;
		}
	} else {
		log_error("Failed to load mq for dev %s", bdf_str);
	}

	return 0;
}

int virtnet_device_mq_save(struct virtnet_device *dev, uint8_t mq)
{
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_file_create(bdf_str);
	if (ret) {
		log_error("Failed to create recovery file for dev %s", bdf_str);
		return ret;
	}

	ret = virtnet_rec_mq_save(bdf_str, mq);
	if (ret)
		log_error("Failed to save mq for dev %s", bdf_str);

	return ret;
}

static void virtnet_rec_host_index_get(const struct virtnet_device *dev,
					const struct snap_cross_mkey *q_mkey,
					int idx)
{
	struct vring_avail vra;
	struct vring_used vru;
	int ret;

	log_debug("queue[%d], avail host addr: 0x%lx, used host addr: 0x%lx", idx,
		dev->registers->q_attrs[idx].vattr.driver,
		dev->registers->q_attrs[idx].vattr.device);

	if (dev->registers->q_attrs[idx].vattr.driver &&
		dev->registers->q_attrs[idx].vattr.device) {
		ret = snap_virtio_get_vring_indexes_from_host(
			dev->sf_verbs.pd,
			dev->registers->q_attrs[idx].vattr.driver,
			dev->registers->q_attrs[idx].vattr.device, q_mkey->mkey,
			&vra, &vru);
		if (!ret) {
			log_debug("queue[%d], avail index: %u, used index: %u", idx,
								vru.idx, vru.idx);
			dev->registers->q_attrs[idx].hw_available_index = vru.idx;
			dev->registers->q_attrs[idx].hw_used_index = vru.idx;
		}
	}
}

int virtnet_device_queue_index_recover(const struct virtnet_device *dev,
					int num_eth_queues, bool ctrl_queue,
					uint16_t ctrl_queue_idx)
{
	struct snap_virtio_net_queue_attr *q_attrs;
	struct snap_cross_mkey *q_mkey;
	int ret, i;

	q_attrs = dev->reg_vqs;

	q_mkey = snap_create_cross_mkey(dev->sf_verbs.pd,
						dev->snap_ctrl->common.sdev);
	if (!q_mkey) {
		log_error("Failed to create snap MKey Entry for queue\n");
		return -EINVAL;
	}

	for (i = 0; i < num_eth_queues; i++) {
		if (!q_attrs[i].vattr.enable)
			continue;
		virtnet_rec_host_index_get(dev, q_mkey, i);
	}

	if (ctrl_queue) {
		if (!q_attrs[ctrl_queue_idx].vattr.enable) {
			log_error("Control VQ isn't enabled");
			return -EINVAL;
		}
		virtnet_rec_host_index_get(dev, q_mkey, ctrl_queue_idx);
	}

	ret = snap_destroy_cross_mkey(q_mkey);
	if (ret) {
		log_error("Failed to destroy mkey");
		return ret;
	}

	return 0;
}

int virtnet_rec_sf_cleanup(struct virtnet_port_ctx *port_ctx)
{
	struct mlxdevm_port_list_head plist_head;
	struct mlxdevm_port_list *cur;
	struct mlxdevm *devm;
	int ret;

	TAILQ_INIT(&plist_head);

	devm = port_ctx->devm;

	ret = mlxdevm_sf_port_list_dump(devm, &plist_head);
	if (ret)
		return ret;

	TAILQ_FOREACH(cur, &plist_head, entry) {
		if (cur->port.sfnum < VIRTNET_SF_BASE_ID)
			continue;
		log_debug("Remove SF: pf_num %d, sf_num %d",
			  cur->port.pfnum, cur->port.sfnum);

		ret = mlxdevm_port_fn_state_set(port_ctx->devm,
					  &cur->port, 0);
		if (ret)
			return ret;
		ret = mlxdevm_port_fn_opstate_wait_detached(port_ctx->devm,
								&cur->port);
		if (ret)
			return ret;
		mlxdevm_sf_port_list_item_del(port_ctx->devm, &plist_head, cur);
	}

	return 0;
}

int virtnet_device_sf_recover(struct virtnet_device *dev, uint32_t *pf_num,
								uint32_t *sf_num)
{
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_pf_num_load(bdf_str, pf_num);
	if (ret) {
		log_error("Failed to load pf num for dev %s", bdf_str);
		return ret;
	}

	ret = virtnet_rec_sf_num_load(bdf_str, sf_num);
	if (ret) {
		log_error("Failed to load sf num for dev %s", bdf_str);
		return ret;
	}

	dev->port_ctx->max_sf_num = MAX(dev->port_ctx->max_sf_num, *sf_num + 1);
	dev->sf_verbs.sf_num = *sf_num;

	log_debug("Recovering sf for dev %s with sf num %d", bdf_str, *sf_num);

	return 0;
}

int virtnet_device_sf_save(struct virtnet_device *dev, uint32_t pf_num,
								uint32_t sf_num)
{
	struct virtnet_rec data = {};
	enum rec_key keys[] = {
		REC_KEY_PF_NUM,
		REC_KEY_SF_NUM,
	};
	char bdf_str[STR_SIZE];
	int ret;

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	ret = virtnet_rec_file_create(bdf_str);
	if (ret) {
		log_error("Failed to create recovery file for dev %s", bdf_str);
		return ret;
	}

	data.pf_num = dev->port_ctx->pci_function;
	data.sf_num = dev->sf_verbs.sf_num;

	ret = virtnet_rec_config_save(bdf_str, keys,
				sizeof(keys)/sizeof(enum rec_key),
				&data);
	if (ret) {
		log_error("Failed to save sf for dev %s", bdf_str);
		return ret;
	}

	return 0;
}

struct file_list_head flist_head = SLIST_HEAD_INITIALIZER(flist_head);

void virtnet_device_dump_all_rec_files(void)
{
	struct file_list *cur;
	struct dirent *dir;
	DIR *d;

	d = opendir(RECOVERY_PATH);
	if (!d)
		return;

	while ((dir = readdir(d)) != NULL) {
		if (dir->d_type != DT_REG)
			continue;

		cur = (struct file_list *)calloc(1,
						sizeof(struct file_list));
		if (!cur)
			return;
		strncpy(cur->bdf, dir->d_name, STR_SIZE);
		cur->valid = false;
		SLIST_INSERT_HEAD(&flist_head, cur, entry);
	}
	closedir(d);
}

void virtnet_device_mark_rec_file(struct virtnet_device *dev)
{
	struct file_list *cur;
	char bdf_str[STR_SIZE];

	virtnet_get_bdf(dev, bdf_str, sizeof(bdf_str));

	SLIST_FOREACH(cur, &flist_head, entry) {
		if (!strncmp(cur->bdf, bdf_str, STR_SIZE)) {
			cur->valid = true;
			break;
		}
	}
}

void virtnet_device_cleanup_unused_rec_files(void)
{
	struct file_list *file;
	struct file_list *ptr;

	file = SLIST_FIRST(&flist_head);

	while (file) {
		if (!file->valid) {
			log_info("Delete unused recovery file %s", file->bdf);
			virtnet_rec_file_del(file->bdf);
		}
		file = SLIST_NEXT(file, entry);
	}

	while (!SLIST_EMPTY(&flist_head)) {
		ptr = SLIST_FIRST(&flist_head);
		SLIST_REMOVE_HEAD(&flist_head, entry);
		free(ptr);
	}
}
